/*前边完成的是同步日志器：直接将日志消息进行格式化写入文件
  接下来完成的是异步日志器：
     思想：
       为了避免因为写日志的过程阻塞，导致业务线程在写日志的时候影响效率，
       因此异步的思想就是不让业务线程进行日志的实际落地操作，而是将日志消息放到缓冲区（一块指定的内存）中
       接下来有一个专门的异步线程，去针对缓冲区中的数据进行处理（实际的落地操作）
     实现：
       1、实现一个线程安全的缓冲区
       2、创建一个异步工作线程，专门负责缓冲区中日志消息的落地操作
     缓冲区详细设计：
       1、使用队列，缓存日志消息，逐条处理
          要去：不能涉及到空间的频繁申请与释放，否则会降低效率
          结果：设计一个环形队列（提前将空间申请好，然后对空间循环利用）
          问题1：这个缓冲区的操作会涉及到多线程，因此缓冲区的操作必须保证线程安全
          线程安全实现：对于缓冲区的读写加锁
            因为写日志操作，在实际开发中，并不会分配太多资源，所以工作线程只需要一个日志器有一个就行
            涉及到的锁冲突：生产者与生产者的互斥 & 生产者与消费者的互斥
          问题2：锁冲突较为严重，因为所以线程之间都存在互斥关系
            解决方案：双缓冲区，一个任务写入缓冲区，一个任务处理缓冲区
                     异步工作线程将任务处理缓冲区中的任务处理完毕后，然后交换缓冲区（或者其中的数据）
                     只有在交换的时候，生产者与消费者才会产生一次锁冲突

  单个缓冲区的进一步设计：
    设计一个缓冲区：直接存放格式化后的日志消息字符串
      好处：
        1、减少了LogMsg对象频繁的构造的消耗
        2、可以针对缓冲区中的日志消息，一次性进行IO操作，减少IO次数，提高效率
      缓冲区类的设计：
        1、管理一个存放字符串数据的缓冲区（使用vector进行空间管理）
        2、当前的写入位置的指针（指向可写区域的起始位置，避免数据的写入覆盖）
        3、当前的读取数据位置的指针（指向可读数据区域的起始位置，当读取指针与写入指针指向相同位置表示数据取完了）
      提供的操作：
        1、向缓冲区中写入数据
        2、获取可读数据起始地址的接口（从缓冲区读取操作（读取指定长度数据，或者读取所有数据）把数据从缓冲区中读出来，就要拿一块缓冲区保存起来，就需要重新拷贝一次）
        3、获取可读数据长度的接口
        4、移动读写位置的接口
        5、初始化缓冲区的操作（将读写位置初始化--将一个缓冲区所有数据处理完毕之后）
        6、提供交换缓冲区的操作（交换空间地址，并不交换空间数据）
*/

/*实现异步日志缓冲区*/

#ifndef __M_BUF_H__
#define __M_BUF_H__
#include "util.hpp"
#include <vector>
#include <cassert>
namespace bitlog
{
#define DEFAULT_BUFFER_SIZE (1 * 1024 * 1024)   // 1M
#define THRESHOLD_BUFFER_SIZE (8 * 1024 * 1024) // 8M,阈值
#define INCREMENT_BUFFER_SIZE (1 * 1024 * 1024) // 增量,线性增长，每次增长1M
    class Buffer
    {
    public:
        Buffer()
            : _buffer(DEFAULT_BUFFER_SIZE),
              _reader_idx(0),
              _writer_idx(0) {}

        // 向缓冲区写入数据
        void push(const char *data, size_t len)
        {
            // 缓冲区剩余空间不够的情况：1、扩容。2、阻塞/返回false
            // （扩容主要用于测试，实际开发中，资源不能无限制使用，是一件非常危险的事情，比如某个程序进入错误死循环，缓存中错误资源不断增多）
            // 1.1、固定大小，则自己返回
            // if (len > writeAbleSize())
            //     return;
            // 1.2、动态空间，用于极限性能测试--扩容
            // if (len > writeAbleSize())
            //      _buffer.resize(_buffer.size() + len); // 实际中，如果频繁扩容，不会扩容一个len，会设计一个阈值，阈值这前，翻倍增长，大于这个值，线性增长
            // 1、考虑空间不够则扩容
            ensureEnoughSize(len); // 空间不够，直接扩容
            // 1、将数据拷贝进缓冲区
            std::copy(data, data + len, &_buffer[_writer_idx]);
            // 2、将当前写入位置向后偏移
            moveWriter(len);
        }

        // 获取可写的空间大小
        size_t writeAbleSize()
        {
            // 对于扩容思路来说，不存在可写空间大小，因为总是可写的
            // 因此这个接口仅仅针对固定大小缓冲区提供
            return (_buffer.size() - _writer_idx);
        }

        // 返回可读数据的起始地址
        const char *begin()
        {
            return &_buffer[_reader_idx];
        }

        // 返回可读数据的长度
        size_t readAbleSize()
        {
            // 因为当前实际的缓冲区设计思想是双缓冲区，处理完就交换，所以不存在空间循环使用的
            return (_writer_idx - _reader_idx); // 不是环形的，是持续向后写的，写完后，那边操作完了，会进行一次交换，所以不存在循环写入，中间相差的长度就是实际的长度
        }

        void moveReader(size_t len)
        {
            assert(len <= readAbleSize());
            _reader_idx += len;
        }

        // 重置读写位置，初始化缓冲区
        void reset()
        {
            _writer_idx = 0; // 缓冲区所以空间都是空闲的  
            _reader_idx = 0;   // 与_writer_idx相等表示没有数据可读 
        }

        // 对Buffer实现交换操作(交换的是地址)
        void swap(Buffer &buffer)
        {
            _buffer.swap(buffer._buffer);
            std::swap(_reader_idx, buffer._reader_idx);
            std::swap(_writer_idx, buffer._writer_idx);
        }

        // 判断缓冲区是否为空
        bool empty()
        {
            return (_reader_idx == _writer_idx);
        }

    private:
        // 对空间进行扩容
        void ensureEnoughSize(size_t len)
        {
            if (len <= writeAbleSize()) // 不需要扩容
                return;
            size_t new_size = 0;
            if (_buffer.size() < THRESHOLD_BUFFER_SIZE)
            {
                new_size = _buffer.size() * 2 + len; // 小于阈值则翻倍增长，翻倍增长依然小于len，那这时候就不合适了，其实不存在这种情况，但是不得不考虑，所以可以简单处理直接加len，或循环处理
            }
            else
            {
                new_size = _buffer.size() + INCREMENT_BUFFER_SIZE + len; // 否则线性增长
            }
            _buffer.resize(new_size);
        }
        // 对读写指针进行向后偏移操作
        void moveWriter(size_t len)
        {
            assert((len + _writer_idx) <= _buffer.size());
            _writer_idx += len;
        }

    private:
        std::vector<char> _buffer;
        size_t _reader_idx; // 当前可读数据的指针--本质是下标
        size_t _writer_idx; // 当前可写数据的指针
    };
}

#endif